<?php
/**
 * +-------------------------------------------------------------
 * |Caption:
 * +-------------------------------------------------------------
 * |Author: Lenny
 * +-------------------------------------------------------------
 * |Email:  1685964109@qq.com
 * +-------------------------------------------------------------
 * |Date:   2024/7/27 上午10:27
 * +-------------------------------------------------------------
 */

namespace app\common\controller;

use app\BaseController;
use app\common\exception\BaseException;
use app\common\traits\CurdTrait;
use app\common\traits\JsonTrait;
use app\common\validate\CurdValidate;
use think\db\Query;
use think\facade\Db;
use think\helper\Str;
use think\model\relation\BelongsToMany;

class CommonController extends BaseController
{
    use JsonTrait;
    use CurdTrait;

    /**
     * 是否忽略权限验证
     * @var bool
     */
    protected bool $ignoreAuth = false;

    /**
     * 当前模型
     * @Model
     * @var object
     */
    protected object $model;

    /**
     * 验证类名
     * @var string
     */
    protected string $validateClass = '';

    /**
     * 自定义验证规则
     */
    protected array $validateRule = [];


    /**
     * @var array
     */
    protected array $sort = [
        //            'sort_order'  => 'desc',
        'create_time' => 'desc',
    ];

    /**
     * 是否关联查询
     * @var bool
     */
    protected bool $relationSearch = false;

    /**
     * 查询字段
     * 实际上也可以作为参数，指定前端传递要查询的字段，但get请求对参数长度实际有限制
     * @var string
     */
    protected string $searchFields = '';

    /**
     * 查询关联表字段
     */
    protected array $joinSearchFields = [];

    /**
     * 关联查询的表名
     * @var string
     */
    protected string $joinTable = '';

    /**
     * 关联新增的表名
     * @var string
     */
    protected string $joinAddTable = '';

    /**
     * 关联更新的表名
     * @var string
     */
    protected string $joinUpdateTable = '';

    /**
     * 关联删除的表名
     * @var string
     */
    protected string $joinDelTable = '';

    /**
     * 是否重新组装列表数据
     * @var bool
     */
    protected bool $each = false;

    /**
     * 当前登录的管理员信息
     * @var array
     */
    protected array $userInfo = [];

    /**
     * 当前登录的管理员ID
     * @var int|string
     */
    protected int|string $uid = 0;

    /**
     * 是否需要登录
     * @var bool
     */
    protected bool $needLogin = true;

    /**
     * 不导出的字段信息
     * @var array
     */
    protected array $noExportFields = ['delete_time', 'update_time'];

    /**
     * 导入的模版的文件header
     */
    protected array $importHeader = [];

    /**
     * 是否自动化处理导入
     */
    protected bool $autoImport = false;

    /**
     * 默认模版
     */
    protected array $defaultTemplate = [];

    /**
     * 模版名称
     */
    protected string $fileName;

    /**
     * 默认条件
     */
    protected array $defaultWhere = [];


    /**
     * 通用验证方法
     * @param array $data 待验证的数据
     * @param string $scene 验证场景
     * @param bool $batch 是否批量验证
     */
    protected function sceneValidate(array $data, string $scene, bool $batch = false): bool
    {
        if ($scene == 'modify' || $scene == 'del') $this->validateClass = CurdValidate::class;

        if ($this->validateClass) {
            return validate($this->validateClass)->scene($scene)->batch($batch)->check($data);
        }

        if (isset($data['id'])) {
            $this->validateRule['id'] = 'require';
            $this->validateRule       = array_map(function ($item)
            {
                return explode('|', $item)[0];
            }, $this->validateRule);
        }
        if ($this->validateRule) {
            $this->validate($data, $this->validateRule);
        }
        return true;
    }

    /**
     * 构建查询条件配置
     * '=','%*%','*%','%*','range','fis','in','>','>=','<','<=','!='
     * @return array
     */
    protected function buildSearchParams(): array
    {
        // 获取请求参数
        $get     = $this->request->get();
        $limit   = $get['limit'] ?? 15;
        $filters = json_decode(htmlspecialchars_decode($get['filter'] ?? '{}'), true);
        $ops     = json_decode(htmlspecialchars_decode($get['op'] ?? '{}'), true);

        $mainWhere = []; // 主表查询条件
        $joinWhere = []; // 关联表查询条件

        if (!empty($filters)) {
            foreach ($filters as $key => $val) {
                // 跳过无效值
                if (empty($val) || (is_array($val) && empty($val[0]) && empty($val[1]))) {
                    continue;
                }
                $op = strtolower($ops[$key] ?? '%*%');

                // 根据操作符处理条件
                switch ($op) {
                    case '%*%':
                    case '*%':
                        $this->handleLike($op, $key, $val, $joinWhere, $mainWhere);
                        break;
                    case 'range':
                        $this->handleRange($key, $val, $joinWhere, $mainWhere);
                        break;
                    case 'fis':
                        $this->handleFis($key, $val, $joinWhere, $mainWhere);
                        break;
                    case 'in':
                        $this->handleIn($key, $val, $joinWhere, $mainWhere);
                        break;
                    default:
                        $this->handleDefault($op, $key, $val, $joinWhere, $mainWhere);
                        break;
                }
            }
        }
        return [$limit, $mainWhere, $joinWhere];
    }

    /**
     * 处理模糊匹配
     * @param string $op
     * @param string $key
     * @param $val
     * @param array $joinWhere
     * @param array $mainWhere
     * @return void
     */
    protected function handleLike(string $op, string $key, $val, array &$joinWhere, array &$mainWhere): void
    {
        $placeholder = ($op === '%*%' ? "%{$val}%" : "{$val}%");
        $condition   = [$key, 'like', $placeholder];
        $this->addToWhere($key, $condition, $joinWhere, $mainWhere);
    }

    /**
     * 处理范围查询
     * @param string $key
     * @param $val
     * @param array $joinWhere
     * @param array $mainWhere
     * @return void
     */
    protected function handleRange(string $key, $val, array &$joinWhere, array &$mainWhere): void
    {
        if (!is_array($val) || count($val) < 2) {
            return;
        }

        list($beginTime, $endTime) = $val;
        $startCondition = [$key, '>=', strtotime($beginTime)];
        $endCondition   = [$key, '<=', strtotime($endTime)];

        $this->addToWhere($key, $startCondition, $joinWhere, $mainWhere);
        $this->addToWhere($key, $endCondition, $joinWhere, $mainWhere);
    }

    /**
     * 处理 FIND_IN_SET 查询
     * @param string $key
     * @param $val
     * @param array $joinWhere
     * @param array $mainWhere
     * @return void
     */
    protected function handleFis(string $key, $val, array &$joinWhere, array &$mainWhere): void
    {
        $condition = function ($query) use ($key, $val)
        {
            $query->whereRaw("FIND_IN_SET(?, `$key`) > 0", [$val]);
        };

        $this->addToWhere($key, $condition, $joinWhere, $mainWhere);
    }

    /**
     * 处理 IN 查询
     * @param string $key
     * @param $val
     * @param array $joinWhere
     * @param array $mainWhere
     * @return void
     */
    protected function handleIn(string $key, $val, array &$joinWhere, array &$mainWhere): void
    {
        if (empty($val)) {
            return;
        }

        $valArray  = is_array($val) ? $val : explode(',', $val);
        $condition = [$key, 'in', $valArray];

        $this->addToWhere($key, $condition, $joinWhere, $mainWhere);
    }

    /**
     * 处理默认操作符
     * @param string $op
     * @param string $key
     * @param $val
     * @param array $joinWhere
     * @param array $mainWhere
     * @return void
     */
    protected function handleDefault(string $op, string $key, $val, array &$joinWhere, array &$mainWhere): void
    {
        $condition = [$key, $op, $val];
        $this->addToWhere($key, $condition, $joinWhere, $mainWhere);
    }

    /**
     * 辅助函数，根据字段名决定添加到主表条件还是关联表条件
     * @param string $key
     * @param $condition
     * @param array $joinWhere
     * @param array|null $mainWhere
     * @return void
     */
    protected function addToWhere(string $key, $condition, array &$joinWhere, array &$mainWhere = null): void
    {
        if (str_contains($key, '.')) {
            list($relation, $field) = explode('.', $key, 2);
            list(, $op, $value) = $condition;
            $joinWhere[$relation][] = [$field, $op, $value];
        } else {
            if ($mainWhere !== null) {
                $mainWhere[] = $condition;
            }
        }
    }

    /**
     * 构建自定义查询条件
     * @param $query
     * @param $mainWhere
     * @return mixed
     */
    public function buildCustomCondition($query, $mainWhere): mixed
    {
        if ($this->defaultWhere) $query = $query->where($this->buildFields($this->defaultWhere));
        // 筛选查询字段
        if (!empty($this->searchFields)) $query = $query->field($this->buildFields($this->searchFields));
        // 添加主表查询条件
        if (!empty($mainWhere)) $query = $query->where($mainWhere);
        return $query;
    }

    /**
     * 字段处理
     * @param $fields
     * @param null $table
     * @return array
     */
    protected function buildFields($fields, $table = null): array
    {
        if (is_string($fields)) $fields = explode(',', $fields);
        return array_map(function ($item) use ($table)
        {
            if (!$table) $table = $this->model->getName();
            if (is_array($item)) {
                list($filed, $op, $value) = $item;
                return [$table . '.' . $filed, $op, $value];
            } else {
                return $table . '.' . $item;
            }
        }, $fields);
    }

    /**
     * 通用列表查询
     * @param Query $query
     * @param array $conditions
     * @param $table
     * @return void
     */
    protected function buildCondition(Query $query, array $conditions, $table = null): void
    {
        foreach ($conditions as $condition) {
            list($field, $op, $value) = $condition;
            // 在字段名前加上表名或别名前缀
            $query->where($field, $op, $value);
            if ($table) $this->visibleJoinFields($query, $table);
        }
    }

    /**
     * 过滤掉指定关联表字段
     * @param $query
     * @param $tableName
     * @return void
     */
    protected function visibleJoinFields($query, $tableName): void
    {
        if (!empty($this->joinSearchFields) && isset($this->joinSearchFields[$tableName])) $query->visible($this->joinSearchFields[$tableName]);
    }

    /**
     * 构建模型关联查询条件
     * @param $query
     * @param $joinTables
     * @param $joinWhere
     * @param $limit
     * @return mixed
     */
    protected function buildRelationCondition($query, $joinTables, $joinWhere, $limit): mixed
    {
        $withTables = [];
        // 判断是否需要关联查询
        $remainingTables = array_diff($joinTables, array_keys($joinWhere));
        if (empty($joinWhere)) $joinWhere = $remainingTables;
        foreach ($joinWhere as $tableName => $fields) {
            if (is_string($tableName)) {
                $withTables[$tableName] = function (Query $query) use ($tableName, $fields)
                {
                    // 过滤掉指定关联表字段
                    $this->visibleJoinFields($query, $tableName);
                };
                // 判断模型关联类
                $relationModel = $this->model->$tableName();
                if ($relationModel instanceof BelongsToMany) {
                    // 多对多模型处理
                    $name = $relationModel->getName();
                    $query->whereExists(function ($query) use ($name, $fields)
                    {
                        foreach ($fields as $item) {
                            list($field, $op, $value) = $item;
                            $query->name($name)->alias($name)
                                  ->where($name . '.' . $field, $op, $value)
                                  ->where($this->buildFields($this->defaultWhere, $name));
                        }
                    });
                } else {
                    $query = $query->hasWhere($tableName, function (Query $query) use ($tableName, $fields)
                    {
                        $this->buildCondition($query, $fields, $tableName);
                    });
                }
            }
            // 处理剩余的关联表
            foreach ($remainingTables as $tableName) {
                $withTables[$tableName] = function (Query $query) use ($tableName)
                {
                    $this->visibleJoinFields($query, $tableName);
                };
            }
        }
        $query = $query->with($withTables);
        return $query->order($this->sort)->paginate($limit);
    }

    /**
     * 严格校验接口是否为POST请求
     * @throws BaseException
     */
    protected function checkPost(): void
    {
        if (!$this->request->isPost()) {
            throw new BaseException(['message' => '当前请求不合法！']);
        }
    }

    /**
     * 关联新增
     * @param $post
     * @return void
     * @throws BaseException
     */
    public function joinAddTable($post): void
    {
        foreach (explode(',', $this->joinAddTable) as $table) {
            // 关联模型字段格式化
            $tableField = json_decode($post[$table], true);

            // 字段空验证
            if (!$tableField) throw new BaseException(['message' => "{$table}字段格式不正确！"]);

            // 匹配数据格式
            if (count($tableField, COUNT_RECURSIVE) === count($tableField)) {
                $this->model->$table()->save($tableField);
            } else {
                // 批量新增
                $this->model->$table()->saveAll($tableField);
            }
        }
    }

    /**
     * 关联更新
     * @param $post
     * @param $row
     * @return void
     * @throws BaseException
     */
    public function joinUpdateTable($post, $row): void
    {
        foreach (explode(',', $this->joinUpdateTable) as $table) {
            // 关联模型字段格式化
            $tableField = json_decode($post[$table], true);
            // 字段空验证
            if (!$tableField) throw new BaseException(['message' => "{$table}字段格式不正确！"]);
            // 匹配数据格式  TP的大坑 更新子表或将没有提交的字段重置默认值，底层是先删除后新增
            //            if (count($tableField, COUNT_RECURSIVE) === count($tableField)) {
            //                $row->$table()->save($tableField);
            //            } else {
            //                $row->$table()->saveAll($tableField);
            //            }

            if (count($tableField, COUNT_RECURSIVE) === count($tableField)) {
                if (isset($tableField['id']) && $tableField['id']) {
                    $updateTable = $row->$table()->find($tableField['id']);
                    if ($updateTable) $updateTable->update($tableField);
                } else {
                    $row->$table()->save($tableField);
                }
            } else {
                array_walk($tableField, function ($value) use ($row, $table)
                {
                    if (isset($value['id']) && $value['id']) {
                        $updateTable = $row->$table()->find($value['id']);
                        if ($updateTable) $updateTable->update($value);
                    } else
                        $row->$table()->save($value);
                });
            }
        }
    }
}